#include "SharedCacheView.h"
#include <regex>
#include <filesystem>

#include "SharedCacheController.h"
#include "FileAccessorCache.h"
#include "MappedFileAccessor.h"
#include "SharedCacheBuilder.h"

using namespace BinaryNinja;
using namespace BinaryNinja::DSC;

SharedCacheViewType::SharedCacheViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {}

// We register all our one-shot stuff here, such as the object destructor.
void SharedCacheViewType::Register()
{
	auto fdLimit = AdjustFileDescriptorLimit();
	LogDebugF("Shared Cache processing initialized with a max file descriptor limit of {}", fdLimit);

	// Adjust the global accessor cache to the fdlimit.
	FileAccessorCache::Global().SetCacheSize(fdLimit);
	
	RegisterSharedCacheControllerDestructor();

	static SharedCacheViewType type;
	BinaryViewType::Register(&type);
}

Ref<BinaryView> SharedCacheViewType::Create(BinaryView* data)
{
	return new SharedCacheView(VIEW_NAME, data, false);
}

Ref<Settings> SharedCacheViewType::GetLoadSettingsForData(BinaryView* data)
{
	Ref<BinaryView> viewRef = Parse(data);
	if (!viewRef || !viewRef->Init())
	{
		LogWarnF("Failed to initialize view of type '{}'. Generating default load settings.", GetName().c_str());
		viewRef = data;
	}

	Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef);

	// specify default load settings that can be overridden
	std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"};
	settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable.");

	for (const auto& override : overrides)
	{
		if (settings->Contains(override))
			settings->UpdateProperty(override, "readOnly", false);
	}

	settings->RegisterSetting("loader.dsc.processCFStrings",
		R"({
		"title" : "Process CFString Metadata",
		"type" : "boolean",
		"default" : true,
		"description" : "Processes CoreFoundation strings, applying string values from encoded metadata"
		})");

	settings->RegisterSetting("loader.dsc.autoLoadPattern",
		R"({
		"title" : "Image Auto-Load Regex Pattern",
		"type" : "string",
		"default" : ".*libsystem_c.dylib",
		"description" : "A regex pattern to auto load matching images at the end of view init, defaults to the system_c image only."
		})");

	settings->RegisterSetting("loader.dsc.processObjC",
		R"({
		"title" : "Process Objective-C Metadata",
		"type" : "boolean",
		"default" : true,
		"description" : "Processes Objective-C metadata, applying class and method names from encoded metadata"
		})");

	settings->RegisterSetting("loader.dsc.regionFilter",
	R"({
		"title" : "Region Regex Filter",
		"type" : "string",
		"default" : ".*LINKEDIT.*",
		"description" : "Regex filter for region names to skip loading, by default this filters out the link edit region which is not necessary to be applied to the view."
		})");

	settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements",
		R"({
		"title" : "Auto-Load Objective-C Stub Requirements",
		"type" : "boolean",
		"default" : true,
		"description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on."
		})");

	settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData",
		R"({
		"title" : "Auto-Load Stub Islands",
		"type" : "boolean",
		"default" : true,
		"description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on."
		})");

	settings->RegisterSetting("loader.dsc.processFunctionStarts",
		R"({
			"title" : "Process Mach-O Function Starts Tables",
			"type" : "boolean",
			"default" : true,
			"description" : "Add function starts sourced from the Function Starts tables to the core for analysis."
			})");

	// Place the synthetic sections well after the shared cache to ensure they do
	// not collide with any images that are later loaded from the shared cache.
	// We do not have easy access to the size of the shared cache's mapping here
	// so we use a large fixed offset.
	// TODO: This will have to be updated if we add support for 32-bit shared caches.
	const uint64_t syntheticSectionsOffset = 12ull * 1024 * 1024 * 1024;
	settings->UpdateProperty("loader.syntheticSectionBase", "default", viewRef->GetStart() + syntheticSectionsOffset);

	// Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O
	// Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This
	// schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema
	// contains 'loader.macho.universalImageOffset'.
	Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName());
	if (loadSettings && !loadSettings->IsEmpty())
		settings->DeserializeSchema(loadSettings->SerializeSchema());

	return settings;
}

Ref<BinaryView> SharedCacheViewType::Parse(BinaryView* data)
{
	return new SharedCacheView(VIEW_NAME, data, true);
}

bool SharedCacheViewType::IsTypeValidForData(BinaryView* data)
{
	if (!data)
		return false;

	DataBuffer sig = data->ReadBuffer(data->GetStart(), 4);
	if (sig.GetLength() != 4)
		return false;

	const char* magic = (char*)sig.GetData();
	if (strncmp(magic, "dyld", 4) == 0)
		return true;

	return false;
}

SharedCacheView::SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly) :
	BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly)
{
	// By default, we will assume the primary file name from the original file path.
	// This is subject to be overridden via `LoadMetadata`.
	m_primaryFileName = BaseFileName(GetFile()->GetOriginalFilename());

	// Load up the metadata from the parent view (because our metadata is hilariously not available during view init)
	if (const auto metadata = GetParentView()->QueryMetadata(VIEW_METADATA_KEY))
		LoadMetadata(*metadata);
}

enum DSCPlatform
{
	DSCPlatformMacOS = 1,
	DSCPlatformiOS = 2,
	DSCPlatformTVOS = 3,
	DSCPlatformWatchOS = 4,
	DSCPlatformBridgeOS = 5,  // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS.
	// DSCPlatformMacCatalyst = 6,
	DSCPlatformiOSSimulator = 7,
	DSCPlatformTVOSSimulator = 8,
	DSCPlatformWatchOSSimulator = 9,
	DSCPlatformDriverKit = 10,
	DSCPlatformVisionOS = 11,          // Apple Vision Pro
	DSCPlatformVisionOSSimulator = 12  // Apple Vision Pro Simulator
};

bool SharedCacheView::Init()
{
	std::string os;
	std::string arch;

	m_logger = new Logger("SharedCache.View", GetFile()->GetSessionId());

	char magic[17];
	GetParentView()->Read(&magic, 0, 16);
	magic[16] = 0;

	if (std::string(magic) == "dyld_v1   arm64" || std::string(magic) == "dyld_v1  arm64e"
		|| std::string(magic) == "dyld_v1arm64_32")
	{
		arch = "aarch64";
	}
	else if (std::string(magic) == "dyld_v1  x86_64" || std::string(magic) == "dyld_v1 x86_64h")
	{
		arch = "x86_64";
	}
	else
	{
		m_logger->LogErrorF("Unknown magic: {:?}", magic);
		return false;
	}

	// Use the value of mappingOffset as an upper bound for the size of the
	// header to avoid misinterpreting bytes outside of the header.
	uint32_t mappingOffset;
	GetParentView()->Read(&mappingOffset, 0x10, 4);

	uint32_t platform;
	if (mappingOffset >= 0xd8 + 4) {
		GetParentView()->Read(&platform, 0xd8, 4);
	} else {
		m_logger->LogWarn("Old header without platform field: Defaulting to iOS");
		platform = DSCPlatformiOS;
	}

	switch (platform)
	{
	case DSCPlatformMacOS:
	case DSCPlatformTVOS:
	case DSCPlatformTVOSSimulator:
		os = "mac";
		break;
	case DSCPlatformiOS:
	case DSCPlatformiOSSimulator:
	case DSCPlatformVisionOS:
	case DSCPlatformVisionOSSimulator:
		os = "ios";
		break;
	case DSCPlatformDriverKit:
		// TODO: The same platform value is used for both macOS and iOS DriverKit shared caches.
		// Until we have a way to differentiate them, default to macOS.
		os = "mac";
		break;
	// armv7 or slide info v1 (unsupported)
	case DSCPlatformWatchOS:
	case DSCPlatformWatchOSSimulator:
	case DSCPlatformBridgeOS:
		m_logger->LogErrorF("Unsupported platform: {}", platform);
		return false;
	default:
		m_logger->LogWarnF("Unknown platform: {}", platform);
		return false;
	}

	SetDefaultPlatform(Platform::GetByName(os + "-" + arch));
	SetDefaultArchitecture(Architecture::GetByName(arch));

	Ref<Settings> settings = GetLoadSettings(GetTypeName());

	if (!settings)
	{
		Ref<Settings> programSettings = Settings::Instance();
		auto previousWorkflow = programSettings->Get<std::string>("analysis.workflows.functionWorkflow", this);

		// Earlier versions of the shared cache plug-in used a named workflow rather than
		// modifying `core.function.metaAnalysis`. Update reference to those older workflow
		// names to `core.function.metaAnalysis`.
		if (previousWorkflow == "core.function.dsc" || previousWorkflow == "core.function.sharedCache")
			programSettings->Set("analysis.workflows.functionWorkflow", "core.function.metaAnalysis", this);
	}

	if (m_parseOnly)
		return true;

	QualifiedNameAndType headerType;
	std::string err;

	ParseTypeString(
		"\n"
		"\tstruct dyld_cache_header\n"
		"\t{\n"
		"\t\tchar magic[16];\t\t\t\t\t // e.g. \"dyld_v0    i386\"\n"
		"\t\tuint32_t mappingOffset;\t\t\t // file offset to first dyld_cache_mapping_info\n"
		"\t\tuint32_t mappingCount;\t\t\t // number of dyld_cache_mapping_info entries\n"
		"\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from "
	    "crashing\n"
		"\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from "
	    "crashing\n"
		"\t\tuint64_t dyldBaseAddress;\t\t // base address of dyld when cache was built\n"
		"\t\tuint64_t codeSignatureOffset;\t // file offset of code signature blob\n"
		"\t\tuint64_t codeSignatureSize;\t\t // size of code signature blob (zero means to end of file)\n"
		"\t\tuint64_t slideInfoOffsetUnused;\t // unused.  Used to be file offset of kernel slid info\n"
		"\t\tuint64_t slideInfoSizeUnused;\t // unused.  Used to be size of kernel slid info\n"
		"\t\tuint64_t localSymbolsOffset;\t // file offset of where local symbols are stored\n"
		"\t\tuint64_t localSymbolsSize;\t\t // size of local symbols information\n"
		"\t\tuint8_t uuid[16];\t\t\t\t // unique value for each shared cache file\n"
		"\t\tuint64_t cacheType;\t\t\t\t // 0 for development, 1 for production // Kat: , 2 for iOS 16?\n"
		"\t\tuint32_t branchPoolsOffset;\t\t // file offset to table of uint64_t pool addresses\n"
		"\t\tuint32_t branchPoolsCount;\t\t // number of uint64_t entries\n"
		"\t\tuint64_t accelerateInfoAddr;\t // (unslid) address of optimization info\n"
		"\t\tuint64_t accelerateInfoSize;\t // size of optimization info\n"
		"\t\tuint64_t imagesTextOffset;\t\t // file offset to first dyld_cache_image_text_info\n"
		"\t\tuint64_t imagesTextCount;\t\t // number of dyld_cache_image_text_info entries\n"
		"\t\tuint64_t patchInfoAddr;\t\t\t // (unslid) address of dyld_cache_patch_info\n"
		"\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the "
	    "dyld_cache_patch_info\n"
		"\t\tuint64_t otherImageGroupAddrUnused;\t // unused\n"
		"\t\tuint64_t otherImageGroupSizeUnused;\t // unused\n"
		"\t\tuint64_t progClosuresAddr;\t\t\t // (unslid) address of list of program launch closures\n"
		"\t\tuint64_t progClosuresSize;\t\t\t // size of list of program launch closures\n"
		"\t\tuint64_t progClosuresTrieAddr;\t\t // (unslid) address of trie of indexes into program launch closures\n"
		"\t\tuint64_t progClosuresTrieSize;\t\t // size of trie of indexes into program launch closures\n"
		"\t\tuint32_t platform;\t\t\t\t\t // platform number (macOS=1, etc)\n"
		"\t\tuint32_t formatVersion : 8,\t\t\t // dyld3::closure::kFormatVersion\n"
		"\t\t\tdylibsExpectedOnDisk : 1,  // dyld should expect the dylib exists on disk and to compare inode/mtime to "
	    "see if cache is valid\n"
		"\t\t\tsimulator : 1,\t\t\t   // for simulator of specified platform\n"
		"\t\t\tlocallyBuiltCache : 1,\t   // 0 for B&I built cache, 1 for locally built cache\n"
		"\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables "
	    "must be used for overrides\n"
		"\t\t\tpadding : 20;\t\t\t\t // TBD\n"
		"\t\tuint64_t sharedRegionStart;\t\t // base load address of cache if not slid\n"
		"\t\tuint64_t sharedRegionSize;\t\t // overall size required to map the cache and all subCaches, if any\n"
		"\t\tuint64_t maxSlide;\t\t\t\t // runtime slide of cache can be between zero and this value\n"
		"\t\tuint64_t dylibsImageArrayAddr;\t // (unslid) address of ImageArray for dylibs in this cache\n"
		"\t\tuint64_t dylibsImageArraySize;\t // size of ImageArray for dylibs in this cache\n"
		"\t\tuint64_t dylibsTrieAddr;\t\t // (unslid) address of trie of indexes of all cached dylibs\n"
		"\t\tuint64_t dylibsTrieSize;\t\t // size of trie of cached dylib paths\n"
		"\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen "
	    "closures\n"
		"\t\tuint64_t otherImageArraySize;\t // size of ImageArray for dylibs and bundles with dlopen closures\n"
		"\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen "
	    "closures\n"
		"\t\tuint64_t otherTrieSize;\t // size of trie of dylibs and bundles with dlopen closures\n"
		"\t\tuint32_t mappingWithSlideOffset;\t\t // file offset to first dyld_cache_mapping_and_slide_info\n"
		"\t\tuint32_t mappingWithSlideCount;\t\t\t // number of dyld_cache_mapping_and_slide_info entries\n"
		"\t\tuint64_t dylibsPBLStateArrayAddrUnused;\t // unused\n"
		"\t\tuint64_t dylibsPBLSetAddr;\t\t\t\t // (unslid) address of PrebuiltLoaderSet of all cached dylibs\n"
		"\t\tuint64_t programsPBLSetPoolAddr;\t\t // (unslid) address of pool of PrebuiltLoaderSet for each program\n"
		"\t\tuint64_t programsPBLSetPoolSize;\t\t // size of pool of PrebuiltLoaderSet for each program\n"
		"\t\tuint64_t programTrieAddr;\t\t\t\t // (unslid) address of trie mapping program path to PrebuiltLoaderSet\n"
		"\t\tuint32_t programTrieSize;\n"
		"\t\tuint32_t osVersion;\t\t\t\t// OS Version of dylibs in this cache for the main platform\n"
		"\t\tuint32_t altPlatform;\t\t\t// e.g. iOSMac on macOS\n"
		"\t\tuint32_t altOsVersion;\t\t\t// e.g. 14.0 for iOSMac\n"
		"\t\tuint64_t swiftOptsOffset;\t\t// file offset to Swift optimizations header\n"
		"\t\tuint64_t swiftOptsSize;\t\t\t// size of Swift optimizations header\n"
		"\t\tuint32_t subCacheArrayOffset;\t// file offset to first dyld_subcache_entry\n"
		"\t\tuint32_t subCacheArrayCount;\t// number of subCache entries\n"
		"\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local "
	    "symbols\n"
		"\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add "
	    "read-only/executable data\n"
		"\t\tuint64_t rosettaReadOnlySize;\t// maximum size of the Rosetta read-only/executable region\n"
		"\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write "
	    "data\n"
		"\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n"
		"\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n"
		"\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n"
		"\t\tuint32_t cacheSubType;           // 0 for development, 1 for production, when cacheType is "
	    "multi-cache(2)\n"
		"\t\tuint64_t objcOptsOffset;         // VM offset from cache_header* to ObjC optimizations header\n"
		"\t\tuint64_t objcOptsSize;           // size of ObjC optimizations header\n"
		"\t\tuint64_t cacheAtlasOffset;       // VM offset from cache_header* to embedded cache atlas for process "
	    "introspection\n"
		"\t\tuint64_t cacheAtlasSize;         // size of embedded cache atlas\n"
		"\t\tuint64_t dynamicDataOffset;      // VM offset from cache_header* to the location of "
	    "dyld_cache_dynamic_data_header\n"
		"\t\tuint64_t dynamicDataMaxSize;     // maximum size of space reserved from dynamic data\n"
		"\t\tuint32_t tproMappingsOffset;     // file offset to first dyld_cache_tpro_mapping_info\n"
		"\t\tuint32_t tproMappingsCount;      // number of dyld_cache_tpro_mapping_info entries\n"
		"\t};",
		headerType, err);

	if (!err.empty() || !headerType.type)
	{
		m_logger->LogErrorF("Failed to parse header type: {}", err);
		return false;
	}

	// Add Mach-O file header type info
	EnumerationBuilder cpuTypeBuilder;
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_VAX", MACHO_CPU_TYPE_VAX);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC680x0", MACHO_CPU_TYPE_MC680x0);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86", MACHO_CPU_TYPE_X86);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86_64", MACHO_CPU_TYPE_X86_64);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MIPS", MACHO_CPU_TYPE_MIPS);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC98000", MACHO_CPU_TYPE_MC98000);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_HPPA", MACHO_CPU_TYPE_HPPA);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM", MACHO_CPU_TYPE_ARM);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64", MACHO_CPU_TYPE_ARM64);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64_32", MACHO_CPU_TYPE_ARM64_32);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC88000", MACHO_CPU_TYPE_MC88000);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_SPARC", MACHO_CPU_TYPE_SPARC);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_I860", MACHO_CPU_TYPE_I860);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ALPHA", MACHO_CPU_TYPE_ALPHA);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC", MACHO_CPU_TYPE_POWERPC);
	cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC64", MACHO_CPU_TYPE_POWERPC64);
	Ref<Enumeration> cpuTypeEnum = cpuTypeBuilder.Finalize();

	Ref<Type> cpuTypeEnumType = Type::EnumerationType(nullptr, cpuTypeEnum, 4, false);
	std::string cpuTypeEnumName = "cpu_type_t";
	std::string cpuTypeEnumId = Type::GenerateAutoTypeId("macho", cpuTypeEnumName);
	DefineType(cpuTypeEnumId, cpuTypeEnumName, cpuTypeEnumType);

	EnumerationBuilder fileTypeBuilder;
	fileTypeBuilder.AddMemberWithValue("MH_OBJECT", MH_OBJECT);
	fileTypeBuilder.AddMemberWithValue("MH_EXECUTE", MH_EXECUTE);
	fileTypeBuilder.AddMemberWithValue("MH_FVMLIB", MH_FVMLIB);
	fileTypeBuilder.AddMemberWithValue("MH_CORE", MH_CORE);
	fileTypeBuilder.AddMemberWithValue("MH_PRELOAD", MH_PRELOAD);
	fileTypeBuilder.AddMemberWithValue("MH_DYLIB", MH_DYLIB);
	fileTypeBuilder.AddMemberWithValue("MH_DYLINKER", MH_DYLINKER);
	fileTypeBuilder.AddMemberWithValue("MH_BUNDLE", MH_BUNDLE);
	fileTypeBuilder.AddMemberWithValue("MH_DYLIB_STUB", MH_DYLIB_STUB);
	fileTypeBuilder.AddMemberWithValue("MH_DSYM", MH_DSYM);
	fileTypeBuilder.AddMemberWithValue("MH_KEXT_BUNDLE", MH_KEXT_BUNDLE);
	fileTypeBuilder.AddMemberWithValue("MH_FILESET", MH_FILESET);
	Ref<Enumeration> fileTypeEnum = fileTypeBuilder.Finalize();

	Ref<Type> fileTypeEnumType = Type::EnumerationType(nullptr, fileTypeEnum, 4, false);
	std::string fileTypeEnumName = "file_type_t";
	std::string fileTypeEnumId = Type::GenerateAutoTypeId("macho", fileTypeEnumName);
	DefineType(fileTypeEnumId, fileTypeEnumName, fileTypeEnumType);

	EnumerationBuilder flagsTypeBuilder;
	flagsTypeBuilder.AddMemberWithValue("MH_NOUNDEFS", MH_NOUNDEFS);
	flagsTypeBuilder.AddMemberWithValue("MH_INCRLINK", MH_INCRLINK);
	flagsTypeBuilder.AddMemberWithValue("MH_DYLDLINK", MH_DYLDLINK);
	flagsTypeBuilder.AddMemberWithValue("MH_BINDATLOAD", MH_BINDATLOAD);
	flagsTypeBuilder.AddMemberWithValue("MH_PREBOUND", MH_PREBOUND);
	flagsTypeBuilder.AddMemberWithValue("MH_SPLIT_SEGS", MH_SPLIT_SEGS);
	flagsTypeBuilder.AddMemberWithValue("MH_LAZY_INIT", MH_LAZY_INIT);
	flagsTypeBuilder.AddMemberWithValue("MH_TWOLEVEL", MH_TWOLEVEL);
	flagsTypeBuilder.AddMemberWithValue("MH_FORCE_FLAT", MH_FORCE_FLAT);
	flagsTypeBuilder.AddMemberWithValue("MH_NOMULTIDEFS", MH_NOMULTIDEFS);
	flagsTypeBuilder.AddMemberWithValue("MH_NOFIXPREBINDING", MH_NOFIXPREBINDING);
	flagsTypeBuilder.AddMemberWithValue("MH_PREBINDABLE", MH_PREBINDABLE);
	flagsTypeBuilder.AddMemberWithValue("MH_ALLMODSBOUND", MH_ALLMODSBOUND);
	flagsTypeBuilder.AddMemberWithValue("MH_SUBSECTIONS_VIA_SYMBOLS", MH_SUBSECTIONS_VIA_SYMBOLS);
	flagsTypeBuilder.AddMemberWithValue("MH_CANONICAL", MH_CANONICAL);
	flagsTypeBuilder.AddMemberWithValue("MH_WEAK_DEFINES", MH_WEAK_DEFINES);
	flagsTypeBuilder.AddMemberWithValue("MH_BINDS_TO_WEAK", MH_BINDS_TO_WEAK);
	flagsTypeBuilder.AddMemberWithValue("MH_ALLOW_STACK_EXECUTION", MH_ALLOW_STACK_EXECUTION);
	flagsTypeBuilder.AddMemberWithValue("MH_ROOT_SAFE", MH_ROOT_SAFE);
	flagsTypeBuilder.AddMemberWithValue("MH_SETUID_SAFE", MH_SETUID_SAFE);
	flagsTypeBuilder.AddMemberWithValue("MH_NO_REEXPORTED_DYLIBS", MH_NO_REEXPORTED_DYLIBS);
	flagsTypeBuilder.AddMemberWithValue("MH_PIE", MH_PIE);
	flagsTypeBuilder.AddMemberWithValue("MH_DEAD_STRIPPABLE_DYLIB", MH_DEAD_STRIPPABLE_DYLIB);
	flagsTypeBuilder.AddMemberWithValue("MH_HAS_TLV_DESCRIPTORS", MH_HAS_TLV_DESCRIPTORS);
	flagsTypeBuilder.AddMemberWithValue("MH_NO_HEAP_EXECUTION", MH_NO_HEAP_EXECUTION);
	flagsTypeBuilder.AddMemberWithValue("MH_APP_EXTENSION_SAFE", _MH_APP_EXTENSION_SAFE);
	flagsTypeBuilder.AddMemberWithValue("MH_NLIST_OUTOFSYNC_WITH_DYLDINFO", _MH_NLIST_OUTOFSYNC_WITH_DYLDINFO);
	flagsTypeBuilder.AddMemberWithValue("MH_SIM_SUPPORT", _MH_SIM_SUPPORT);
	flagsTypeBuilder.AddMemberWithValue("MH_DYLIB_IN_CACHE", _MH_DYLIB_IN_CACHE);
	Ref<Enumeration> flagsTypeEnum = flagsTypeBuilder.Finalize();

	Ref<Type> flagsTypeEnumType = Type::EnumerationType(nullptr, flagsTypeEnum, 4, false);
	std::string flagsTypeEnumName = "flags_type_t";
	std::string flagsTypeEnumId = Type::GenerateAutoTypeId("macho", flagsTypeEnumName);
	DefineType(flagsTypeEnumId, flagsTypeEnumName, flagsTypeEnumType);

	StructureBuilder machoHeaderBuilder;
	machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "magic");
	machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("cpu_type_t")), "cputype");
	machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "cpusubtype");
	machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("file_type_t")), "filetype");
	machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "ncmds");
	machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "sizeofcmds");
	machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("flags_type_t")), "flags");
	if (GetAddressSize() == 8)
		machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "reserved");
	Ref<Structure> machoHeaderStruct = machoHeaderBuilder.Finalize();
	QualifiedName headerName = (GetAddressSize() == 8) ? std::string("mach_header_64") : std::string("mach_header");

	std::string headerTypeId = Type::GenerateAutoTypeId("macho", headerName);
	Ref<Type> machoHeaderType = Type::StructureType(machoHeaderStruct);
	DefineType(headerTypeId, headerName, machoHeaderType);

	EnumerationBuilder cmdTypeBuilder;
	cmdTypeBuilder.AddMemberWithValue("LC_REQ_DYLD", LC_REQ_DYLD);
	cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT", LC_SEGMENT);
	cmdTypeBuilder.AddMemberWithValue("LC_SYMTAB", LC_SYMTAB);
	cmdTypeBuilder.AddMemberWithValue("LC_SYMSEG", LC_SYMSEG);
	cmdTypeBuilder.AddMemberWithValue("LC_THREAD", LC_THREAD);
	cmdTypeBuilder.AddMemberWithValue("LC_UNIXTHREAD", LC_UNIXTHREAD);
	cmdTypeBuilder.AddMemberWithValue("LC_LOADFVMLIB", LC_LOADFVMLIB);
	cmdTypeBuilder.AddMemberWithValue("LC_IDFVMLIB", LC_IDFVMLIB);
	cmdTypeBuilder.AddMemberWithValue("LC_IDENT", LC_IDENT);
	cmdTypeBuilder.AddMemberWithValue("LC_FVMFILE", LC_FVMFILE);
	cmdTypeBuilder.AddMemberWithValue("LC_PREPAGE", LC_PREPAGE);
	cmdTypeBuilder.AddMemberWithValue("LC_DYSYMTAB", LC_DYSYMTAB);
	cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLIB", LC_LOAD_DYLIB);
	cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLIB", LC_ID_DYLIB);
	cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLINKER", LC_LOAD_DYLINKER);
	cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLINKER", LC_ID_DYLINKER);
	cmdTypeBuilder.AddMemberWithValue("LC_PREBOUND_DYLIB", LC_PREBOUND_DYLIB);
	cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES", LC_ROUTINES);
	cmdTypeBuilder.AddMemberWithValue("LC_SUB_FRAMEWORK", LC_SUB_FRAMEWORK);
	cmdTypeBuilder.AddMemberWithValue("LC_SUB_UMBRELLA", LC_SUB_UMBRELLA);
	cmdTypeBuilder.AddMemberWithValue("LC_SUB_CLIENT", LC_SUB_CLIENT);
	cmdTypeBuilder.AddMemberWithValue("LC_SUB_LIBRARY", LC_SUB_LIBRARY);
	cmdTypeBuilder.AddMemberWithValue("LC_TWOLEVEL_HINTS", LC_TWOLEVEL_HINTS);
	cmdTypeBuilder.AddMemberWithValue("LC_PREBIND_CKSUM", LC_PREBIND_CKSUM);
	cmdTypeBuilder.AddMemberWithValue("LC_LOAD_WEAK_DYLIB", LC_LOAD_WEAK_DYLIB);  //       (0x18 | LC_REQ_DYLD)
	cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_64", LC_SEGMENT_64);
	cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES_64", LC_ROUTINES_64);
	cmdTypeBuilder.AddMemberWithValue("LC_UUID", LC_UUID);
	cmdTypeBuilder.AddMemberWithValue("LC_RPATH", LC_RPATH);  //                 (0x1c | LC_REQ_DYLD)
	cmdTypeBuilder.AddMemberWithValue("LC_CODE_SIGNATURE", LC_CODE_SIGNATURE);
	cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_SPLIT_INFO", LC_SEGMENT_SPLIT_INFO);
	cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB);  //        (0x1f | LC_REQ_DYLD)
	cmdTypeBuilder.AddMemberWithValue("LC_LAZY_LOAD_DYLIB", LC_LAZY_LOAD_DYLIB);
	cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO", LC_ENCRYPTION_INFO);
	cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO", LC_DYLD_INFO);
	cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY);        //        (0x22 | LC_REQ_DYLD)
	cmdTypeBuilder.AddMemberWithValue("LC_LOAD_UPWARD_DYLIB", LC_LOAD_UPWARD_DYLIB);  //     (0x23 | LC_REQ_DYLD)
	cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_MACOSX", LC_VERSION_MIN_MACOSX);
	cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_IPHONEOS", LC_VERSION_MIN_IPHONEOS);
	cmdTypeBuilder.AddMemberWithValue("LC_FUNCTION_STARTS", LC_FUNCTION_STARTS);
	cmdTypeBuilder.AddMemberWithValue("LC_DYLD_ENVIRONMENT", LC_DYLD_ENVIRONMENT);
	cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN);  //                  (0x28 | LC_REQ_DYLD)
	cmdTypeBuilder.AddMemberWithValue("LC_DATA_IN_CODE", LC_DATA_IN_CODE);
	cmdTypeBuilder.AddMemberWithValue("LC_SOURCE_VERSION", LC_SOURCE_VERSION);
	cmdTypeBuilder.AddMemberWithValue("LC_DYLIB_CODE_SIGN_DRS", LC_DYLIB_CODE_SIGN_DRS);
	cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO_64", _LC_ENCRYPTION_INFO_64);
	cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTION", _LC_LINKER_OPTION);
	cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTIMIZATION_HINT", _LC_LINKER_OPTIMIZATION_HINT);
	cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_TVOS", _LC_VERSION_MIN_TVOS);
	cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_WATCHOS", LC_VERSION_MIN_WATCHOS);
	cmdTypeBuilder.AddMemberWithValue("LC_NOTE", LC_NOTE);
	cmdTypeBuilder.AddMemberWithValue("LC_BUILD_VERSION", LC_BUILD_VERSION);
	cmdTypeBuilder.AddMemberWithValue("LC_DYLD_EXPORTS_TRIE", LC_DYLD_EXPORTS_TRIE);
	cmdTypeBuilder.AddMemberWithValue("LC_DYLD_CHAINED_FIXUPS", LC_DYLD_CHAINED_FIXUPS);
	cmdTypeBuilder.AddMemberWithValue("LC_FILESET_ENTRY", LC_FILESET_ENTRY);
	Ref<Enumeration> cmdTypeEnum = cmdTypeBuilder.Finalize();

	Ref<Type> cmdTypeEnumType = Type::EnumerationType(nullptr, cmdTypeEnum, 4, false);
	std::string cmdTypeEnumName = "load_command_type_t";
	std::string cmdTypeEnumId = Type::GenerateAutoTypeId("macho", cmdTypeEnumName);
	DefineType(cmdTypeEnumId, cmdTypeEnumName, cmdTypeEnumType);

	StructureBuilder loadCommandBuilder;
	loadCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	loadCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	Ref<Structure> loadCommandStruct = loadCommandBuilder.Finalize();
	QualifiedName loadCommandName = std::string("load_command");
	std::string loadCommandTypeId = Type::GenerateAutoTypeId("macho", loadCommandName);
	Ref<Type> loadCommandType = Type::StructureType(loadCommandStruct);
	DefineType(loadCommandTypeId, loadCommandName, loadCommandType);

	EnumerationBuilder protTypeBuilder;
	protTypeBuilder.AddMemberWithValue("VM_PROT_NONE", MACHO_VM_PROT_NONE);
	protTypeBuilder.AddMemberWithValue("VM_PROT_READ", MACHO_VM_PROT_READ);
	protTypeBuilder.AddMemberWithValue("VM_PROT_WRITE", MACHO_VM_PROT_WRITE);
	protTypeBuilder.AddMemberWithValue("VM_PROT_EXECUTE", MACHO_VM_PROT_EXECUTE);
	// protTypeBuilder.AddMemberWithValue("VM_PROT_DEFAULT", MACHO_VM_PROT_DEFAULT);
	// protTypeBuilder.AddMemberWithValue("VM_PROT_ALL", MACHO_VM_PROT_ALL);
	protTypeBuilder.AddMemberWithValue("VM_PROT_NO_CHANGE", MACHO_VM_PROT_NO_CHANGE);
	protTypeBuilder.AddMemberWithValue("VM_PROT_COPY_OR_WANTS_COPY", MACHO_VM_PROT_COPY);
	// protTypeBuilder.AddMemberWithValue("VM_PROT_WANTS_COPY", MACHO_VM_PROT_WANTS_COPY);
	Ref<Enumeration> protTypeEnum = protTypeBuilder.Finalize();

	Ref<Type> protTypeEnumType = Type::EnumerationType(nullptr, protTypeEnum, 4, false);
	std::string protTypeEnumName = "vm_prot_t";
	std::string protTypeEnumId = Type::GenerateAutoTypeId("macho", protTypeEnumName);
	DefineType(protTypeEnumId, protTypeEnumName, protTypeEnumType);

	EnumerationBuilder segFlagsTypeBuilder;
	segFlagsTypeBuilder.AddMemberWithValue("SG_HIGHVM", SG_HIGHVM);
	segFlagsTypeBuilder.AddMemberWithValue("SG_FVMLIB", SG_FVMLIB);
	segFlagsTypeBuilder.AddMemberWithValue("SG_NORELOC", SG_NORELOC);
	segFlagsTypeBuilder.AddMemberWithValue("SG_PROTECTED_VERSION_1", SG_PROTECTED_VERSION_1);
	Ref<Enumeration> segFlagsTypeEnum = segFlagsTypeBuilder.Finalize();

	Ref<Type> segFlagsTypeEnumType = Type::EnumerationType(nullptr, segFlagsTypeEnum, 4, false);
	std::string segFlagsTypeEnumName = "sg_flags_t";
	std::string segFlagsTypeEnumId = Type::GenerateAutoTypeId("macho", segFlagsTypeEnumName);
	DefineType(segFlagsTypeEnumId, segFlagsTypeEnumName, segFlagsTypeEnumType);

	StructureBuilder loadSegmentCommandBuilder;
	loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	loadSegmentCommandBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname");
	loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmaddr");
	loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmsize");
	loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "fileoff");
	loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "filesize");
	loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot");
	loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot");
	loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "nsects");
	loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags");
	Ref<Structure> loadSegmentCommandStruct = loadSegmentCommandBuilder.Finalize();
	QualifiedName loadSegmentCommandName = std::string("segment_command");
	std::string loadSegmentCommandTypeId = Type::GenerateAutoTypeId("macho", loadSegmentCommandName);
	Ref<Type> loadSegmentCommandType = Type::StructureType(loadSegmentCommandStruct);
	DefineType(loadSegmentCommandTypeId, loadSegmentCommandName, loadSegmentCommandType);

	StructureBuilder loadSegmentCommand64Builder;
	loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "cmdsize");
	loadSegmentCommand64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname");
	loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmaddr");
	loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmsize");
	loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "fileoff");
	loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "filesize");
	loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot");
	loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot");
	loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "nsects");
	loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags");
	Ref<Structure> loadSegmentCommand64Struct = loadSegmentCommand64Builder.Finalize();
	QualifiedName loadSegment64CommandName = std::string("segment_command_64");
	std::string loadSegment64CommandTypeId = Type::GenerateAutoTypeId("macho", loadSegment64CommandName);
	Ref<Type> loadSegment64CommandType = Type::StructureType(loadSegmentCommand64Struct);
	DefineType(loadSegment64CommandTypeId, loadSegment64CommandName, loadSegment64CommandType);

	StructureBuilder sectionBuilder;
	sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname");
	sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "addr");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "size");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "offset");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "align");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "reloff");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "nreloc");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "flags");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved1");
	sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved2");
	Ref<Structure> sectionStruct = sectionBuilder.Finalize();
	QualifiedName sectionName = std::string("section");
	std::string sectionTypeId = Type::GenerateAutoTypeId("macho", sectionName);
	Ref<Type> sectionType = Type::StructureType(sectionStruct);
	DefineType(sectionTypeId, sectionName, sectionType);

	StructureBuilder section64Builder;
	section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname");
	section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname");
	section64Builder.AddMember(Type::IntegerType(8, false), "addr");
	section64Builder.AddMember(Type::IntegerType(8, false), "size");
	section64Builder.AddMember(Type::IntegerType(4, false), "offset");
	section64Builder.AddMember(Type::IntegerType(4, false), "align");
	section64Builder.AddMember(Type::IntegerType(4, false), "reloff");
	section64Builder.AddMember(Type::IntegerType(4, false), "nreloc");
	section64Builder.AddMember(Type::IntegerType(4, false), "flags");
	section64Builder.AddMember(Type::IntegerType(4, false), "reserved1");
	section64Builder.AddMember(Type::IntegerType(4, false), "reserved2");
	section64Builder.AddMember(Type::IntegerType(4, false), "reserved3");
	Ref<Structure> section64Struct = section64Builder.Finalize();
	QualifiedName section64Name = std::string("section_64");
	std::string section64TypeId = Type::GenerateAutoTypeId("macho", section64Name);
	Ref<Type> section64Type = Type::StructureType(section64Struct);
	DefineType(section64TypeId, section64Name, section64Type);

	StructureBuilder symtabBuilder;
	symtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	symtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	symtabBuilder.AddMember(Type::IntegerType(4, false), "symoff");
	symtabBuilder.AddMember(Type::IntegerType(4, false), "nsyms");
	symtabBuilder.AddMember(Type::IntegerType(4, false), "stroff");
	symtabBuilder.AddMember(Type::IntegerType(4, false), "strsize");
	Ref<Structure> symtabStruct = symtabBuilder.Finalize();
	QualifiedName symtabName = std::string("symtab");
	std::string symtabTypeId = Type::GenerateAutoTypeId("macho", symtabName);
	Ref<Type> symtabType = Type::StructureType(symtabStruct);
	DefineType(symtabTypeId, symtabName, symtabType);

	StructureBuilder dynsymtabBuilder;
	dynsymtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ilocalsym");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocalsym");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iextdefsym");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextdefsym");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iundefsym");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nundefsym");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "tocoff");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ntoc");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "modtaboff");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nmodtab");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extrefsymoff");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrefsyms");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "indirectsymoff");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nindirectsyms");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extreloff");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrel");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "locreloff");
	dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocrel");
	Ref<Structure> dynsymtabStruct = dynsymtabBuilder.Finalize();
	QualifiedName dynsymtabName = std::string("dynsymtab");
	std::string dynsymtabTypeId = Type::GenerateAutoTypeId("macho", dynsymtabName);
	Ref<Type> dynsymtabType = Type::StructureType(dynsymtabStruct);
	DefineType(dynsymtabTypeId, dynsymtabName, dynsymtabType);

	StructureBuilder uuidBuilder;
	uuidBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	uuidBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	uuidBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, false), 16), "uuid");
	Ref<Structure> uuidStruct = uuidBuilder.Finalize();
	QualifiedName uuidName = std::string("uuid");
	std::string uuidTypeId = Type::GenerateAutoTypeId("macho", uuidName);
	Ref<Type> uuidType = Type::StructureType(uuidStruct);
	DefineType(uuidTypeId, uuidName, uuidType);

	StructureBuilder linkeditDataBuilder;
	linkeditDataBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "dataoff");
	linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "datasize");
	Ref<Structure> linkeditDataStruct = linkeditDataBuilder.Finalize();
	QualifiedName linkeditDataName = std::string("linkedit_data");
	std::string linkeditDataTypeId = Type::GenerateAutoTypeId("macho", linkeditDataName);
	Ref<Type> linkeditDataType = Type::StructureType(linkeditDataStruct);
	DefineType(linkeditDataTypeId, linkeditDataName, linkeditDataType);

	StructureBuilder encryptionInfoBuilder;
	encryptionInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptoff");
	encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptsize");
	encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptid");
	Ref<Structure> encryptionInfoStruct = encryptionInfoBuilder.Finalize();
	QualifiedName encryptionInfoName = std::string("encryption_info");
	std::string encryptionInfoTypeId = Type::GenerateAutoTypeId("macho", encryptionInfoName);
	Ref<Type> encryptionInfoType = Type::StructureType(encryptionInfoStruct);
	DefineType(encryptionInfoTypeId, encryptionInfoName, encryptionInfoType);

	StructureBuilder versionMinBuilder;
	versionMinBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	versionMinBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	versionMinBuilder.AddMember(Type::IntegerType(4, false), "version");
	versionMinBuilder.AddMember(Type::IntegerType(4, false), "sdk");
	Ref<Structure> versionMinStruct = versionMinBuilder.Finalize();
	QualifiedName versionMinName = std::string("version_min");
	std::string versionMinTypeId = Type::GenerateAutoTypeId("macho", versionMinName);
	Ref<Type> versionMinType = Type::StructureType(versionMinStruct);
	DefineType(versionMinTypeId, versionMinName, versionMinType);

	StructureBuilder dyldInfoBuilder;
	dyldInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_off");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_size");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_off");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_size");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_off");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_size");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_off");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_size");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_off");
	dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_size");
	Ref<Structure> dyldInfoStruct = dyldInfoBuilder.Finalize();
	QualifiedName dyldInfoName = std::string("dyld_info");
	std::string dyldInfoTypeId = Type::GenerateAutoTypeId("macho", dyldInfoName);
	Ref<Type> dyldInfoType = Type::StructureType(dyldInfoStruct);
	DefineType(dyldInfoTypeId, dyldInfoName, dyldInfoType);

	StructureBuilder dylibBuilder;
	dylibBuilder.AddMember(Type::IntegerType(4, false), "name");
	dylibBuilder.AddMember(Type::IntegerType(4, false), "timestamp");
	dylibBuilder.AddMember(Type::IntegerType(4, false), "current_version");
	dylibBuilder.AddMember(Type::IntegerType(4, false), "compatibility_version");
	Ref<Structure> dylibStruct = dylibBuilder.Finalize();
	QualifiedName dylibName = std::string("dylib");
	std::string dylibTypeId = Type::GenerateAutoTypeId("macho", dylibName);
	Ref<Type> dylibType = Type::StructureType(dylibStruct);
	DefineType(dylibTypeId, dylibName, dylibType);

	StructureBuilder dylibCommandBuilder;
	dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	dylibCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("dylib")), "dylib");
	Ref<Structure> dylibCommandStruct = dylibCommandBuilder.Finalize();
	QualifiedName dylibCommandName = std::string("dylib_command");
	std::string dylibCommandTypeId = Type::GenerateAutoTypeId("macho", dylibCommandName);
	Ref<Type> dylibCommandType = Type::StructureType(dylibCommandStruct);
	DefineType(dylibCommandTypeId, dylibCommandName, dylibCommandType);

	StructureBuilder filesetEntryCommandBuilder;
	filesetEntryCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd");
	filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize");
	filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "vmaddr");
	filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "fileoff");
	filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "entry_id");
	filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "reserved");
	Ref<Structure> filesetEntryCommandStruct = filesetEntryCommandBuilder.Finalize();
	QualifiedName filesetEntryCommandName = std::string("fileset_entry_command");
	std::string filesetEntryCommandTypeId = Type::GenerateAutoTypeId("macho", filesetEntryCommandName);
	Ref<Type> filesetEntryCommandType = Type::StructureType(filesetEntryCommandStruct);
	DefineType(filesetEntryCommandTypeId, filesetEntryCommandName, filesetEntryCommandType);

	// uint32_t at 0x10 is offset to mapping table.
	// first mapping struct in that table is base of primary
	// first uint64_t in that struct is the base address of the primary
	// double gpv here because DSCRaw explicitly stops at the start of this mapping table
	uint64_t basePointer = 0;
	GetParentView()->Read(&basePointer, 16, 4);
	if (basePointer == 0)
	{
		m_logger->LogError("Failed to read base pointer");
		return false;
	}
	uint64_t primaryBase = 0;
	GetParentView()->Read(&primaryBase, basePointer, 8);
	if (primaryBase == 0)
	{
		m_logger->LogErrorF("Failed to read primary base at {:#x}", basePointer);
		return false;
	}

	uint64_t headerSize = std::min(basePointer, headerType.type->GetWidth());
	// Truncate the `dyld_cache_header` structure to the structure present in the cache file.
	auto newStructure = StructureBuilder(headerType.type->GetStructure()).SetWidth(headerSize).Finalize();
	headerType.type = TypeBuilder::StructureType(newStructure).Finalize();

	AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable);
	AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics);
	DefineType("dyld_cache_header", headerType.name, headerType.type);
	DefineAutoSymbolAndVariableOrFunction(
		GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type);

	if (GetFile()->GetFilename().empty())
	{
		// We have initialized the view with no backing file.
		// We are going to forgo initialization of shared cache controller, as there is no way to populate entries.
		// This can occur when downloading shared cache files from a remote project. In the collaboration core it will
		// call view init to push metadata about the remote IIRC.
		LogInfo("No backing file, skipping shared cache controller initialization...");
		return true;
	}

	return InitController();
}

bool SharedCacheView::InitController()
{
	auto primaryFilePath = GetPrimaryFilePath();
	if (!primaryFilePath.has_value())
	{
		m_logger->LogError("Failed to get primary file path!");
		return false;
	}
	std::string primaryFileDir = std::filesystem::path(*primaryFilePath).parent_path().string();

	// Get the primary project file from the current files project.
	// This is required to allow selecting a primary file in a different directory. Otherwise, we search the current database directory.
	Ref<ProjectFile> primaryProjectFile = nullptr;
	auto currentProjectFile = GetFile()->GetProjectFile();
	if (currentProjectFile)
		primaryProjectFile = currentProjectFile->GetProject()->GetFileByPathOnDisk(*primaryFilePath);

	if (!IsSameFolderForFile(primaryProjectFile, currentProjectFile))
	{
		// TODO: Remove this restriction using stored cache UUID's and a fast project file search.
		m_logger->LogWarn("Because the primary file is in a different project folder you will need to select it on every open, consider moving the database file into the same folder.");
	}

	// OK, we have the primary shared cache file, now let's add the entries.
	auto sharedCacheBuilder = SharedCacheBuilder(this);
	sharedCacheBuilder.SetPrimaryFileName(m_primaryFileName);

	// Add the primary file. If we fail log alert that the primary cache file is invalid.
	// We process the primary cache entry first as it might be consulted in the processing of later entries.
	if (!sharedCacheBuilder.AddFile(*primaryFilePath, m_primaryFileName, CacheEntryType::Primary))
	{
		m_logger->LogAlertF("Failed to add primary cache file: '{}'", m_primaryFileName);
		return false;
	}

	{
		// After this we should have all the mappings available as well.
		auto startTime = std::chrono::high_resolution_clock::now();
		if (primaryProjectFile)
			sharedCacheBuilder.AddProjectFolder(primaryProjectFile->GetFolder());
		else
			sharedCacheBuilder.AddDirectory(primaryFileDir);
		auto totalEntries = sharedCacheBuilder.GetCache().GetEntries().size();
		auto endTime = std::chrono::high_resolution_clock::now();
		std::chrono::duration<double> elapsed = endTime - startTime;
		m_logger->LogInfoF("Processing {} entries took {:.3f} seconds", totalEntries, elapsed.count());

		// If we can't store all of our files for this cache in the accessor cache we might run into issues, warn the user.
		if (totalEntries > FileAccessorCache::Global().GetCacheSize())
			m_logger->LogWarn("Cache contains more entries than the allowed number of opened file handles, this may impact reliability.");

		// Verify that we are not missing any entries that were stored in the metadata.
		// If we are that means we should alert the user that a previously associated cache entry is missing.
		std::set<std::string> missingCacheEntries = m_secondaryFileNames;
		std::optional<uint64_t> expectedFileCount;
		for (const auto& entry : sharedCacheBuilder.GetCache().GetEntries())
		{
			missingCacheEntries.erase(entry.GetFileName());
			LogSecondaryFileName(entry.GetFileName());
			// Set the number of sub-caches so we can verify it later.
			if (entry.GetType() == CacheEntryType::Primary)
			{
				const auto& entryHeader = entry.GetHeader();
				if (expectedFileCount)
					m_logger->LogWarnF("Multiple primary cache files found for '{}'.", m_primaryFileName);

				// TODO: Some caches seem to have fewer sub caches than what we report having.
				expectedFileCount = 1 + entryHeader.subCacheArrayCount;
			}
		}
		for (const auto& missingFileName: missingCacheEntries)
			m_logger->LogErrorF("Secondary cache file '{}' is missing!", missingFileName);

		// Verify that we have the required amount of sub-caches, if not alert the user.
		if (!expectedFileCount)
			m_logger->LogWarnF("Did not find a primary cache file for '{}'! You are likely opening a secondary cache file instead of a primary one.", m_primaryFileName);
		else if (totalEntries < expectedFileCount)
			m_logger->LogWarnF("Insufficient cache files in dyld header ({}/{}), loading as partial shared cache...", totalEntries, *expectedFileCount);
	}

	auto sharedCache = sharedCacheBuilder.Finalize();

	{
		// Write all the slide info pointers to the virtual memory.
		// This should be done before any other work begins, as the backing data will be altered by this process.
		auto startTime = std::chrono::high_resolution_clock::now();
		for (const auto& entry : sharedCache.GetEntries())
			sharedCache.ProcessEntrySlideInfo(entry);
		auto endTime = std::chrono::high_resolution_clock::now();
		std::chrono::duration<double> elapsed = endTime - startTime;
		m_logger->LogInfoF("Processing slide info took {:.3f} seconds", elapsed.count());
	}

	{
		// Load up all images into the cache before adding extra regions.
		// Currently, it is expected that a primary entry contain all relevant images, so we only check that one.
		auto startTime = std::chrono::high_resolution_clock::now();
		for (const auto& entry : sharedCache.GetEntries())
			if (entry.GetType() == CacheEntryType::Primary)
				sharedCache.ProcessEntryImages(entry);
		auto endTime = std::chrono::high_resolution_clock::now();
		std::chrono::duration<double> elapsed = endTime - startTime;
		const auto& images = sharedCache.GetImages();
		m_logger->LogInfoF("Processing {} images took {:.3f} seconds", images.size(), elapsed.count());
		// Warn if we found no images, and provide the likely explanation
		if (images.empty())
			m_logger->LogWarn("Failed to process any images, likely missing cache files.");
	}

	{
		// Load up all the regions into the cache.
		auto startTime = std::chrono::high_resolution_clock::now();
		for (const auto& entry : sharedCache.GetEntries())
			sharedCache.ProcessEntryRegions(entry);
		auto endTime = std::chrono::high_resolution_clock::now();
		std::chrono::duration<double> elapsed = endTime - startTime;
		m_logger->LogInfoF("Processing {} regions took {:.3f} seconds", sharedCache.GetRegions().size(), elapsed.count());
	}

	// TODO: Here we should have all the regions and what not for the virtual memory populated, I think it might be a good idea
	// TODO: To verify the regions are here and pointing at valid file accessor mappings, to make diagnosing issues quicker.

	auto cacheController = SharedCacheController::Initialize(*this, std::move(sharedCache));

	{
		auto logger = m_logger;
		// Load up all the symbols into the named symbols lookup map.
		// NOTE: We do this on a separate thread as image & region loading does not consult this.
		WorkerPriorityEnqueue([logger, cacheController]() {
			auto& sharedCache = cacheController->GetCache();
			auto startTime = std::chrono::high_resolution_clock::now();
			sharedCache.ProcessSymbols();
			auto endTime = std::chrono::high_resolution_clock::now();
			std::chrono::duration<double> elapsed = endTime - startTime;
			logger->LogInfoF("Processing {} symbols took {:.3f} seconds (separate thread)", sharedCache.GetSymbols().size(), elapsed.count());
		});
	}

	Ref<Settings> settings = GetLoadSettings(GetTypeName());
	// Users can adjust which images are loaded by default using the `loader.dsc.autoLoadPattern` setting.
	std::string autoLoadPattern = ".*libsystem_c.dylib";
	if (settings && settings->Contains("loader.dsc.autoLoadPattern"))
		autoLoadPattern = settings->Get<std::string>("loader.dsc.autoLoadPattern", this);
	m_logger->LogDebugF("Loading images using pattern: {:?}", autoLoadPattern);

	{
		// TODO: Refusing to add undo action "Added section libsystem_c.dylib::__macho_header", there is literally
		// TODO: no way around this warning as undo actions are implicit with user sections, for more detail see:
		// TODO:	- https://github.com/Vector35/binaryninja-api/issues/6742
		// TODO:	- https://github.com/Vector35/binaryninja-api/issues/6289
		// Load all images that match the `autoLoadPattern`.
		auto startTime = std::chrono::high_resolution_clock::now();
		size_t loadedImages = 0;
		std::regex autoLoadRegex(autoLoadPattern);
		for (const auto& [_, image] : cacheController->GetCache().GetImages())
			if (std::regex_match(image.GetName(), autoLoadRegex))
				if (cacheController->ApplyImage(*this, image))
					++loadedImages;
		auto endTime = std::chrono::high_resolution_clock::now();
		std::chrono::duration<double> elapsed = endTime - startTime;
		m_logger->LogInfoF("Automatically loading {} images took {:.3f} seconds", loadedImages, elapsed.count());
	}

	return true;
}

void SharedCacheView::SetPrimaryFileName(std::string primaryFileName)
{
	m_primaryFileName = std::move(primaryFileName);
	GetParentView()->StoreMetadata(VIEW_METADATA_KEY, GetMetadata());
}

void SharedCacheView::LogSecondaryFileName(std::string secondaryFileName)
{
	m_secondaryFileNames.insert(std::move(secondaryFileName));
	GetParentView()->StoreMetadata(VIEW_METADATA_KEY, GetMetadata());
}

std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
{
	auto viewFile = GetFile();
	// 1. Try and get the primary file path using `GetOriginalFilename`.
	auto primaryFilePath = viewFile->GetOriginalFilename();

	// 2. If the original file name is not a usable file path then prompt the user to select one.
	if (primaryFilePath.empty() || !std::filesystem::exists(primaryFilePath))
	{
		if (!GetOpenFileNameInput(primaryFilePath, "Please select the primary shared cache file"))
			return std::nullopt;
		SetPrimaryFileName(BaseFileName(primaryFilePath));
		// Update so next load we don't need to prompt the user.
		viewFile->SetOriginalFilename(primaryFilePath);
	}

	// 3. If we are not in a project, we can go ahead and return the file path, it does not need to be resolved from project.
	auto primaryProjectFile = viewFile->GetProjectFile();
	if (!primaryProjectFile)
		return primaryFilePath;

	auto project = primaryProjectFile->GetProject();
	auto primaryProjectFileName = primaryProjectFile->GetName();
	auto primaryProjectFilePath = primaryProjectFile->GetPathOnDisk();

	// 4. If we are not a BNDB project file than we can return the path on disk as we are the primary file.
	if (primaryProjectFileName.find(".bndb") == std::string::npos)
	{
		// Set the primary file name to the project file name so on subsequent loads we can pick it up.
		SetPrimaryFileName(primaryProjectFileName);
		return primaryProjectFilePath;
	}

	// 5. If we are a BNDB project file the path must be resolved from the file name.
	auto primaryProjectFileFolder = primaryProjectFile->GetFolder();
	for (const auto& pj : project->GetFiles())
	{
		// Skip files not in the same folder.
		if (!IsSameFolder(pj->GetFolder(), primaryProjectFileFolder))
			continue;
		// We are looking for the file with file name we stored in metadata.
		if (pj->GetName() != m_primaryFileName)
			continue;
		return pj->GetPathOnDisk();
	}

	// 6. If we fail to resolve the project file given the `m_primaryFileName` than we fall back to asking the user.
	std::string newPrimaryFilePath;
	if (!GetOpenFileNameInput(newPrimaryFilePath, "Please select the primary shared cache file"))
		return std::nullopt;

	// TODO: We likely want to verify that the project file exists in the same directory as the BNDB.
	// TODO: We currently require the database to exist in the same directory as the files.
	// Update the primary file name for later loads, otherwise we would keep prompting to select a file.
	primaryProjectFile = project->GetFileByPathOnDisk(newPrimaryFilePath);
	SetPrimaryFileName(primaryProjectFile->GetName());
	return newPrimaryFilePath;
}

Ref<Metadata> SharedCacheView::GetMetadata() const
{
	std::map<std::string, Ref<Metadata>> viewMeta;

	std::vector<std::string> secondaryFileNames;
	secondaryFileNames.reserve(m_secondaryFileNames.size());
	for (const auto& secondaryFileName : m_secondaryFileNames)
		secondaryFileNames.push_back(secondaryFileName);

	// TODO: Refactor this to just "cache files" which is a new struct of:
	// TODO: cache file name
	// TODO: cache file UUID
	// TODO: cache file entry type?
	viewMeta["secondaryFileNames"] = new Metadata(secondaryFileNames);
	viewMeta["primaryFileName"] = new Metadata(m_primaryFileName);

	return new Metadata(viewMeta);
}

void SharedCacheView::LoadMetadata(const Metadata &metadata)
{
	auto viewMeta = metadata.GetKeyValueStore();
	if (viewMeta.find("secondaryFileNames") != viewMeta.end())
	{
		const auto secondaryFileNames = viewMeta["secondaryFileNames"]->GetStringList();
		for (const auto& secondaryFileName : secondaryFileNames)
			m_secondaryFileNames.insert(secondaryFileName);
	}

	if (viewMeta.find("primaryFileName") != viewMeta.end())
		m_primaryFileName = viewMeta["primaryFileName"]->GetString();
}
